package sample.context.orm; import java.time.*; import java.util.*; import java.util.function.Function; import javax.persistence.EntityManager; import javax.persistence.criteria.*; import javax.persistence.metamodel.Metamodel; import org.apache.commons.lang3.StringUtils; import org.hibernate.criterion.MatchMode; import sample.context.orm.Sort.SortOrder; /** * ORM の可変条件を取り扱う CriteriaBuilder ラッパー。 * <p>Criteria の簡易的な取り扱いを可能にします。 * <p>Criteria で利用する条件句は必要に応じて追加してください。 * <p>ビルド結果としての CriteriaQuery は result* メソッドで受け取って下さい。 */ public class OrmCriteria<T> { public static final String DefaultAlias = "m"; private final Class<T> clazz; private final String alias; private final Metamodel metamodel; private final CriteriaBuilder builder; private final CriteriaQuery<T> query; private final Root<T> root; private final Set<Predicate> predicates = new LinkedHashSet<>(); private final Set<Order> orders = new LinkedHashSet<>(); /** 指定したEntityクラスにエイリアスを紐付けたCriteriaを生成します。 */ private OrmCriteria(EntityManager em, Class<T> clazz, String alias) { this.clazz = clazz; this.metamodel = em.getMetamodel(); this.builder = em.getCriteriaBuilder(); this.query = builder.createQuery(clazz); this.root = query.from(clazz); this.alias = alias; this.root.alias(alias); } /** 内部に保有するエンティティクラスを返します。 */ public Class<T> entityClass() { return clazz; } /** エンティティのメタ情報を返します。 */ public Metamodel metamodel() { return metamodel; } /** 内部に保有する CriteriaBuilder を返します。 */ public CriteriaBuilder builder() { return builder; } /** 内部に保有する Root を返します。 */ public Root<T> root() { return root; } /** * 関連付けを行います。 * <p>引数にはJoin可能なフィールド(@ManyToOne 等)を指定してください。 * <p>Join した要素は呼び出し元で保持して必要に応じて利用してください。 */ public <Y> Join<T, Y> join(String associationPath) { return root.join(associationPath); } public <Y> Join<T, Y> join(String associationPath, String alias) { Join<T, Y> v = join(associationPath); v.alias(alias); return v; } /** * 組み上げた CriteriaQuery を返します。 * <p>複雑なクエリや集計関数は本メソッドで返却された query を元に追加構築してください。 */ public CriteriaQuery<T> result() { return result(q -> q); } @SuppressWarnings("unchecked") public CriteriaQuery<T> result(Function<CriteriaQuery<?>, CriteriaQuery<?>> extension) { CriteriaQuery<T> q = query.where(predicates.toArray(new Predicate[0])); q = (CriteriaQuery<T>)extension.apply(q); return orders.isEmpty() ? q : q.orderBy(orders.toArray(new Order[0])); } public CriteriaQuery<Long> resultCount() { return resultCount(q -> q); } @SuppressWarnings("unchecked") public CriteriaQuery<Long> resultCount(Function<CriteriaQuery<?>, CriteriaQuery<?>> extension) { CriteriaQuery<Long> q = builder.createQuery(Long.class); q.from(clazz).alias(alias); q.where(predicates.toArray(new Predicate[0])); if (q.isDistinct()) { q.select(builder.countDistinct(root)); } else { q.select(builder.count(root)); } return (CriteriaQuery<Long>)extension.apply(q); } /** * 条件句 ( or 条件含む ) を追加します。 * <p>引数には CriteriaBuilder で生成した Predicate を追加してください。 */ public OrmCriteria<T> add(final Predicate predicate) { this.predicates.add(predicate); return this; } /** or 条件を付与します。 */ public OrmCriteria<T> or(final Predicate... predicates) { if (predicates.length != 0) { add(builder.or(predicates)); } return this; } /** null 一致条件を付与します。 */ public OrmCriteria<T> isNull(String field) { return add(builder.isNull(root.get(field))); } /** null 不一致条件を付与します。 */ public OrmCriteria<T> isNotNull(String field) { return add(builder.isNotNull(root.get(field))); } /** 一致条件を付与します。( 値が null の時は無視されます ) */ public OrmCriteria<T> equal(String field, final Object value) { return equal(root, field, value); } public OrmCriteria<T> equal(Path<?> path, String field, final Object value) { if (isValid(value)) { add(builder.equal(path.get(field), value)); } return this; } private boolean isValid(final Object value) { if (value instanceof String) { return StringUtils.isNotBlank((String) value); } else if (value instanceof Optional) { return ((Optional<?>) value).isPresent(); } else { return value != null; } } /** 不一致条件を付与します。(値がnullの時は無視されます) */ public OrmCriteria<T> equalNot(String field, final Object value) { if (isValid(value)) { add(builder.notEqual(root.get(field), value)); } return this; } /** 一致条件を付与します。(値がnullの時は無視されます) */ public OrmCriteria<T> equalProp(String field, final String fieldOther) { add(builder.equal(root.get(field), root.get(fieldOther))); return this; } /** like条件を付与します。(値がnullの時は無視されます) */ public OrmCriteria<T> like(String field, String value, MatchMode mode) { if (isValid(value)) { add(builder.like(root.get(field), mode.toMatchString(value))); } return this; } /** like条件を付与します。[複数フィールドに対するOR結合](値がnullの時は無視されます) */ public OrmCriteria<T> like(String[] fields, String value, MatchMode mode) { if (isValid(value)) { Predicate[] predicates = new Predicate[fields.length]; for (int i = 0; i < fields.length; i++) { predicates[i] = builder.like(root.get(fields[i]), mode.toMatchString(value)); } add(builder.or(predicates)); } return this; } /** in条件を付与します。 */ public OrmCriteria<T> in(String field, final Object[] values) { if (values != null && 0 < values.length) { add(root.get(field).in(values)); } return this; } /** between条件を付与します。 */ public OrmCriteria<T> between(String field, final Date from, final Date to) { if (from != null && to != null) { predicates.add(builder.between(root.get(field), from, to)); } return this; } /** between条件を付与します。 */ public OrmCriteria<T> between(String field, final LocalDate from, final LocalDate to) { if (from != null && to != null) { predicates.add(builder.between(root.get(field), from, to)); } return this; } /** between条件を付与します。 */ public OrmCriteria<T> between(String field, final LocalDateTime from, final LocalDateTime to) { if (from != null && to != null) { predicates.add(builder.between(root.get(field), from, to)); } return this; } /** between条件を付与します。 */ public OrmCriteria<T> between(String field, final String from, final String to) { if (isValid(from) && isValid(to)) { predicates.add(builder.between(root.get(field), from, to)); } return this; } /** [フィールド]>=[値] 条件を付与します。(値がnullの時は無視されます) */ public <Y extends Comparable<? super Y>> OrmCriteria<T> gte(String field, final Y value) { if (isValid(value)) { add(builder.greaterThanOrEqualTo(root.get(field), value)); } return this; } /** [フィールド]>[値] 条件を付与します。(値がnullの時は無視されます) */ public <Y extends Comparable<? super Y>> OrmCriteria<T> gt(String field, final Y value) { if (isValid(value)) { add(builder.greaterThan(root.get(field), value)); } return this; } /** [フィールド]<=[値] 条件を付与します。 */ public <Y extends Comparable<? super Y>> OrmCriteria<T> lte(String field, final Y value) { if (isValid(value)) { add(builder.lessThanOrEqualTo(root.get(field), value)); } return this; } /** [フィールド]<[値] 条件を付与します。 */ public <Y extends Comparable<? super Y>> OrmCriteria<T> lt(String field, final Y value) { if (isValid(value)) { add(builder.lessThan(root.get(field), value)); } return this; } /** ソート条件を加えます。 */ public OrmCriteria<T> sort(Sort sort) { sort.getOrders().forEach(this::sort); return this; } /** ソート条件を加えます。 */ public OrmCriteria<T> sort(SortOrder order) { if (order.isAscending()) { sort(order.getProperty()); } else { sortDesc(order.getProperty()); } return this; } /** 昇順条件を加えます。 */ public OrmCriteria<T> sort(String field) { orders.add(builder.asc(root.get(field))); return this; } /** 降順条件を加えます。 */ public OrmCriteria<T> sortDesc(String field) { orders.add(builder.desc(root.get(field))); return this; } public boolean emptySort() { return !orders.isEmpty(); } /** 指定した Entity クラスを軸にしたCriteriaを生成します。 */ public static <T> OrmCriteria<T> of(EntityManager em, Class<T> clazz) { return new OrmCriteria<>(em, clazz, DefaultAlias); } /** 指定した Entity クラスにエイリアスを紐付けたCriteriaを生成します。 */ public static <T> OrmCriteria<T> of(EntityManager em, Class<T> clazz, String alias) { return new OrmCriteria<>(em, clazz, alias); } }